#include "./ota.h"
#include "../driver/flash.h"
#include "../cfg/ota_info.h"
#include "../driver/cmd_uart.h"
#include "../utils/core.h"
#include "../utils/md5.h"

namespace service::ota {

using namespace cfg;
using namespace driver;
using namespace utils;

struct Context {
    uint32_t pkg_size = 0;
    uint32_t pkg_index = 0;
    uint16_t pkg_id = 0;

    void init(uint32_t size) {
        pkg_size = size;
        pkg_index = 0;
        pkg_id = 0;
    }

    Error write(uint16_t id, ByteView &bw) {
        Error err;
        if (id != pkg_id) {
            err = ec::OTA_ID_INVALID;
            goto finish;
        }
        
        err = flash::write(flash::OTA_DATA_ADDR + pkg_index, bw.buf, bw.len);
        if (err) {
            goto finish;
        }
        pkg_id += 1;
        pkg_index += bw.len;
    finish:
        return err;
    }

    bool is_complete() {
        return pkg_index == pkg_size;
    }
};

static Context context;

void start(const conn::RequestRef &msg) {
    Error err;
    uint32_t pkg_size;

    err = msg.parse(pkg_size);
    if (err) {
        goto finish;
    }

    if (pkg_size > flash::APP_SIZE) {
        err = ec::INVALID;
        goto finish;
    }

    err = flash::erase(flash::OTA_DATA_ADDR, pkg_size);
    if (err) {
        goto finish;
    }

    context.init(pkg_size);
finish:
    cmdUart.simple_res(msg, err);
}

void translate(const conn::RequestRef &msg) {
    Error err;
    uint16_t id;
    ByteView bw;

    err = msg.parse(id, bw);
    if (err) {
        goto finish;
    }   

    err = context.write(id, bw);

finish:
    cmdUart.simple_res(msg, err);
}

void complete(const conn::RequestRef &msg) {
    Error err;
    ByteView bw;
    uint8_t md5_sum[16];
    
    if (!context.is_complete()) {
        err = ec::INVALID;
        goto finish;
    }
    
    err = msg.parse(bw);
    if (err) {
        goto finish;
    }

    if (bw.len != 16) {
        err = ec::INVALID;
        goto finish;
    }

    md5::sum(reinterpret_cast<const void *>(flash::OTA_DATA_ADDR), context.pkg_size, md5_sum);
    if (memcmp(md5_sum, bw.buf, 16) != 0) {
        err = ec::OTA_MD5_INVALID;
        goto finish;
    }

finish:
    cmdUart.simple_res(msg, err);

    if (err.is_ok()) {
        osDelay(500);

        otaInfo->init(context.pkg_size);
        otaInfo.save();
        core::run_process(flash::BOOT_ADDR);
    }   
}


}
